export default class Position {
  /*
    计算两点之间的距离（公里数）
    @param {array}   point1 第一个点
    @param {array}   point2 第一个点
    @returns {number}   返回两点之间的公里数
  */
  distanceBetweenTwoPoints(
    point1: number[], // [lng, lat]
    point2: number[] // [lng, lat]
  ) {
    let radLat1 = (point1[1] * Math.PI) / 180.0
    let radLat2 = (point2[1] * Math.PI) / 180.0
    let a = radLat1 - radLat2
    let b = (point1[0] * Math.PI) / 180.0 - (point2[0] * Math.PI) / 180.0
    let s =
      2 *
      Math.asin(
        Math.sqrt(
          Math.pow(Math.sin(a / 2), 2) +
            Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)
        )
      )
    s = s * 6378.137
    s = Math.round(s * 100000) / 100000
    return s - 0
  }

  /*
    计算点到线的距离
    @param {array}   point 点
    @param {array}   lineStart 线起点
    @param {array}   lineEnd 线终点
    @returns {number}   返回点到线的最短距离
  */
  pointToLineDistance(
    point: number[], // [lng, lat]
    lineStart: number[], // [lng, lat]
    lineEnd: number[], // [lng, lat]
  ) {
    // point[0] = point[0] + 0.0000005
    // point[1] = point[1] + 0.0000005
    // 使用向量积计算点到线的最短距离
    // 先计算向量AB和AP
    const ABx = lineEnd[1] - lineStart[1]
    const ABy = lineEnd[0] - lineStart[0]
    const APx = point[1] - lineStart[1]
    const APy = point[0] - lineStart[0]

    // 计算点P到线AB的垂足Q的坐标（使用参数t表示Q的位置）
    // 公式：(A + t*(B-A)) dot AP / ||B-A||^2 = 0
    // 解出t，则Q = A + t*(B-A)
    const t = (APx * ABx + APy * ABy) / (ABx * ABx + ABy * ABy)

    // 计算垂足Q的坐标
    const Qx = lineStart[1] + t * ABx
    const Qy = lineStart[0] + t * ABy

    // 计算线的长度
    let lineLength = this.distanceBetweenTwoPoints(lineStart, lineEnd)
    // 计算垂足坐标距离起点距离
    let QStart = this.distanceBetweenTwoPoints(lineStart, [Qy, Qx])
    // 计算垂足坐标距离终点距离
    let QEnd = this.distanceBetweenTwoPoints(lineEnd, [Qy, Qx])
    // 如果垂足距离起点和终点的距离加起来超过线的长度，那么返回距离起点、终点最近的距离
    if (QStart + QEnd > lineLength) {
      return QStart > QEnd ? QEnd : QStart;
    }

    // 计算PQ的距离（即点到线的最短距离）
    return this.distanceBetweenTwoPoints(point, [Qy, Qx])
  }

  /*
    计算点到线的距离
    @param {array}   point 点
    @param {array}   pts 面界点
    @returns {boolean}   返回点是否在面上
  */
  isPointInPolygon(
    point: number[], // [lng, lat]
    pts: number[][] // [[lng, lat], [lng, lat], ...]
  ) {
    var N = pts.length
    // 如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
    var boundOrVertex = true
    // cross points count of x
    var intersectCount = 0
    // 浮点类型计算时候与0比较时候的容差
    var precision = 2e-10
    // neighbour bound vertices
    var p1, p2
    var p = point

    // left vertex
    p1 = pts[0]
    for (var i = 1; i <= N; ++i) {
      // check all rays
      if (p[1] == p1[1] && p[0] == p1[0]) {
        // p is an vertex
        return boundOrVertex
      }
      // right vertex
      p2 = pts[i % N]
      if (
        p[1] < Math.min(p1[1], p2[1]) ||
        p[1] > Math.max(p1[1], p2[1])
      ) {
        // ray is outside of our interests
        p1 = p2
        // next ray left point
        continue
      }
      if (
        p[1] > Math.min(p1[1], p2[1]) &&
        p[1] < Math.max(p1[1], p2[1])
      ) {
        // ray is crossing over by the algorithm (common part of)
        if (p[0] <= Math.max(p1[0], p2[0])) {
          // x is before of ray
          if (
            p1[1] == p2[1] &&
            p[0] >= Math.min(p1[0], p2[0])
          ) {
            // overlies on a horizontal ray
            return boundOrVertex
          }
          if (p1[0] == p2[0]) {
            // ray is vertical
            if (p1[0] == p[0]) {
              // overlies on a vertical ray
              return boundOrVertex
            } else {
              // before ray
              ++intersectCount
            }
          } else {
            // cross point on the left side
            // cross point of lng
            var xinters = ((p[1] - p1[1]) * (p2[0] - p1[0])) / (p2[1] - p1[1]) + p1[0]
            if (Math.abs(p[0] - xinters) < precision) {
              // overlies on a ray
              return boundOrVertex
            }
            if (p[0] < xinters) {
              // before ray
              ++intersectCount
            }
          }
        }
        // special case when ray is crossing through the vertex
        if (p[1] == p2[1] && p[0] <= p2[0]) {
          // p crossing over p2
          // next vertex
          var p3 = pts[(i + 1) % N]
          if (
            p[1] >= Math.min(p1[1], p3[1]) &&
            p[1] <= Math.max(p1[1], p3[1])
          ) {
            // p.latitude lies between p1.latitude & p3.latitude
            ++intersectCount
          } else {
            intersectCount += 2
          }
        }
      }
      // next ray left point
      p1 = p2
    }
    if (intersectCount % 2 == 0) {
      // 偶数在多边形外
      return false
    } else {
      // 奇数在多边形内
      return true
    }
  }

  /*
    计算线的几何中心点
    @param {array[]}   points 组成线的点
    @returns {array}   返回线的几何中心坐标
  */
  getPolylineCentroid(points: number[][]) {
    let centroidX = 0
    let centroidY = 0
    let count = 0

    // 遍历所有点
    for (let i = 0; i < points.length; i++) {
      const point = points[i];
      centroidX += point[1] // 累加x坐标
      centroidY += point[0] // 累加y坐标
      count++ // 计数
    }

    // 计算平均值
    centroidX /= count
    centroidY /= count

    return [centroidY, centroidX]
  }

  /*
    计算面的几何中心点
    @param {array[]}   points 组成面的点
    @returns {array}   返回面的几何中心坐标
  */
  getPolygonCentroid(points: number[][]) {
    points = [...points]
    points.pop()
    // 假设points是一个包含多边形顶点的数组，每个顶点是一个具有x和y属性的对象
    if (points.length < 3) {
      // throw new Error("至少需要3个顶点来定义一个多边形");
      points.push(points[0])
    }

    let centroidX = 0
    let centroidY = 0

    // 这里只处理了三角形的情况，对于更复杂的多边形，需要更复杂的算法
    if (points.length === 3) {
      // 使用三角形质心公式
      centroidX =
        (points[0][1] + points[1][1] + points[2][1]) / 3;
      centroidY =
        (points[0][0] + points[1][0] + points[2][0]) / 3;
    } else {
      let total = points.length
      let X = 0, Y = 0, Z = 0
      points.forEach((point) => {
        let lng = point[0] * Math.PI / 180
        let lat = point[1] * Math.PI / 180
        let x, y, z
        x = Math.cos(lat) * Math.cos(lng)
        y = Math.cos(lat) * Math.sin(lng)
        z = Math.sin(lat)
        X += x
        Y += y
        Z += z
      })
      X = X / total
      Y = Y / total
      Z = Z / total

      let Lng = Math.atan2(Y, X)
      let Hyp = Math.sqrt(X * X + Y * Y)
      let Lat = Math.atan2(Z, Hyp)
      
      centroidY = Lng * 180 / Math.PI
      centroidX = Lat * 180 / Math.PI
    }

    return [Math.abs(centroidY), Math.abs(centroidX)]
  }
}